package com.book.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.book.core.domain.Books;
import com.book.core.domain.BorrowRecords;
import com.book.core.domain.ReaderRestrictions;
import com.book.core.domain.Readers;
import com.book.core.vo.BookCategoryLikeVO;
import com.book.core.dto.ReaderBorrowBookDTO;
import com.book.core.enums.BorrowRecordType;
import com.book.core.exception.BookReturnException;
import com.book.core.exception.BorrowBookException;
import com.book.core.exception.ReaderException;
import com.book.core.mode.LineBarChart;
import com.book.core.mode.R;
import com.book.core.mode.SysLoginUser;
import com.book.core.query.BorrowRecordQuery;
import com.book.core.query.ReadingRankingQuery;
import com.book.core.vo.PageVO;
import com.book.core.vo.ReadingStarVO;
import com.book.repository.BorrowRecordsMapper;
import com.book.service.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Timestamp;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author hongmizfb
 * @description 针对表【borrow_records(图书借阅记录)】的数据库操作Service实现
 * @createDate 2025-05-08 22:32:22
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class BorrowRecordsServiceImpl extends ServiceImpl<BorrowRecordsMapper, BorrowRecords>
        implements BorrowRecordsService {
    private final ReadersService readersService;
    private final BooksService booksService;
    private final ReaderRestrictionsService readerRestrictionsService;
    private final ReaderTypesService readerTypesService;

    @Override
    @Transactional
    public R newBorrowBook(ReaderBorrowBookDTO dto) {
        List<Long> bookIdList = dto.getBookIdList();
        Long readerId = dto.getReaderId();
//       1 检查用户是否合法
        Readers reader = checkReader(readerId);
        // 2 检查用户是否存在逾期的记录
        boolean canBorrow = readerRestrictionsService.checkCanBorrowByReaderId(readerId);
        if (!canBorrow) {
            throw new BorrowBookException("读者已被限制！");
        }
        //    3.1 检查用户类型借阅是否合法
        Integer count = readerTypesService.getCanBorrowCount(reader.getTypeId());
//        3.2计算用户实际借阅的数量：未归还的+现在借阅的数量
        Long borrowed = lambdaQuery().eq(BorrowRecords::getReaderId, dto.getReaderId()).eq(BorrowRecords::getStatus, BorrowRecordType.BORROWING.getCode()).count();
        if (count < (borrowed + bookIdList.size())) {
            throw new BorrowBookException("超出可借阅数量！");
        }
//       4 查询借阅书籍库存，考虑到借阅图书是线下借书（除非是预约借书），能拿到说明有库存，这步判断是否多余？
        List<Books> errorBooks = new ArrayList<>();
        for (Long bookId : bookIdList) {
            Books byId = booksService.getById(bookId);
            System.out.println("当前图书" + byId);
            if (byId.getStock() <= 0) {
                errorBooks.add(byId);
            } else {
//              5 更新图书库存 此方法非原子性，在高并发情况下有致命缺陷。但为了简化暂不考虑高并发场景
                Books books = new Books();
                books.setBookId(bookId);
                books.setStock(byId.getStock() - 1);
                booksService.updateById(books);
//               6 添加借阅记录
                BorrowRecords records = new BorrowRecords();
                records.setBookId(bookId);
                records.setReaderId(dto.getReaderId());
                records.setBorrowDate(LocalDateTime.now());
//                下一个月归还
                records.setDueDate(LocalDateTime.now().plusMonths(1));
                records.setReturnDate(null);
                records.setStatus(BorrowRecordType.BORROWING.getCode());
                records.setRenewCount(0);
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                SysLoginUser loginUser = (SysLoginUser) authentication.getPrincipal();
                records.setOperatorId(loginUser.getSysUser().getUserId());
                records.setOverdueDays(0L);
                records.setNotes("");
                save(records);
            }
        }
        if (errorBooks.size() > 0) {
            return R.err("借阅失败，以下图书库存不足：" + errorBooks);
        } else return R.ok("借阅成功！", null);
    }

    @Override
    public R<List<Books>> returnBook(List<Long> borrowRecordIdList) {
//        遍历书籍，设置归还
        ArrayList<Books> overdueBooks = new ArrayList<>();
        for (Long borrowRecordId : borrowRecordIdList) {
            BorrowRecords borrowRecords = getById(borrowRecordId);
            if (borrowRecords == null || !BorrowRecordType.BORROWING.getCode().equals(borrowRecords.getStatus())) {
                throw new BookReturnException("借阅记录异常！");
            }
            borrowRecords.setReturnDate(LocalDateTime.now());//实际归还日期
            Books book = booksService.getById(borrowRecords.getBookId());
            if (borrowRecords.getDueDate().isBefore(LocalDateTime.now())) {
//                逾期了，在限制表中添加记录
                ReaderRestrictions readerRestrictions = new ReaderRestrictions();
                readerRestrictions.setRecordId(borrowRecords.getRecordId());
                readerRestrictions.setReaderId(borrowRecords.getReaderId());
                readerRestrictions.setRestrictType(1);
                readerRestrictions.setRestrictDays(Duration.between(borrowRecords.getDueDate(), borrowRecords.getReturnDate()).toDays());
                readerRestrictions.setStartDate(LocalDateTime.now());
                readerRestrictions.setEndDate(LocalDateTime.now().plusDays(readerRestrictions.getRestrictDays()));//限制结束时间
                readerRestrictions.setReason("借阅超期");
                readerRestrictions.setCreatedTime(LocalDateTime.now());
                readerRestrictionsService.save(readerRestrictions);
//                修改借阅记录的逾期天数
                borrowRecords.setOverdueDays(Duration.between(borrowRecords.getDueDate(), borrowRecords.getReturnDate()).toDays());
                overdueBooks.add(book);
            }
            //           归还图书库存
            booksService.lambdaUpdate().eq(Books::getBookId, book).set(Books::getStock, book.getStock() + 1).update();
            borrowRecords.setStatus(1);
            updateById(borrowRecords);
        }
        if (overdueBooks.size() > 0) {
            R.ok("有图书逾期！", overdueBooks);
        }
        return R.ok("归还成功", null);
    }

    @Override
    public R renewBook(List<Long> recordIdList) {
        for (Long recordId : recordIdList) {
            BorrowRecords borrowRecords = getById(recordId);
            borrowRecords.setDueDate(LocalDateTime.now().plusMonths(1));
            borrowRecords.setRenewCount(borrowRecords.getRenewCount() + 1);
            updateById(borrowRecords);
        }
        return R.ok("续借成功!", null);
    }

    @Override
    public PageVO<BorrowRecords> getBorrowRecordPage(BorrowRecordQuery query) {
        dealWithOverdueBorrowRecords();
        Integer status = query.getStatus();
        String readerId = query.getReaderId();
        String bookId = query.getBookId();
        Page<BorrowRecords> page = lambdaQuery().eq(StrUtil.isNotBlank(readerId), BorrowRecords::getReaderId, readerId)
                .eq(status != null && status != -1, BorrowRecords::getStatus, status)
                .eq(StrUtil.isNotBlank(bookId), BorrowRecords::getBookId, bookId)
                .page(query.toMpPage());
        return PageVO.of(page, BorrowRecords.class);
    }

    @Override
    public LineBarChart getBorrowStatisticByTimeRange(String range) {
        LineBarChart lineBarChart = new LineBarChart();
        if (range == null) {
            throw new IllegalArgumentException("时间范围参数不能为空");
        }
        switch (range) {
            case "7d":
                handleLast7Days(lineBarChart);
                break;
            case "30d":
                handleLast30Days(lineBarChart);
                break;
            case "6m":
                handleLast6Months(lineBarChart);
                break;
            case "1y":
                log.error("一年");
                handleLast1Year(lineBarChart);
                break;
            default:
                throw new IllegalArgumentException("不支持的时间范围参数: " + range);
        }
        return lineBarChart;
    }

    @Override
    public List<ReadingStarVO> getReadingStar() {
        ReadingRankingQuery baseQuery = new ReadingRankingQuery();
        baseQuery.setPageNum(1);
        baseQuery.setPageSize(10);
        PageVO<ReadingStarVO> readingRanking = getReadingRanking(baseQuery);
        return readingRanking.getList();
    }

    @Override
    public List<BookCategoryLikeVO> getBookBorrowCount() {
        return baseMapper.getBookBorrowCount();
    }

    public PageVO<ReadingStarVO> getReadingRanking(ReadingRankingQuery query) {
        String timeRange = query.getTimeRange();
        LocalDateTime startDate=LocalDateTime.now().minusWeeks(1);//默认一周
        switch (timeRange) {
            case "1m":
                startDate=LocalDateTime.now().minusMonths(1);
                break;
            case "3m":
                startDate= LocalDateTime.now().minusMonths(3);
                break;
        }
        // 查询数据
        List<ReadingStarVO> list = baseMapper.selectUserBorrowPageByDateRange(
                startDate,
                LocalDateTime.now(),
                query
        );
        Long totalCount = baseMapper.countUserBorrowByDateRange(
                startDate,
                LocalDateTime.now()
        );
        return new PageVO<ReadingStarVO>(Long.valueOf(query.getPageNum()), Long.valueOf(query.getPageSize()), totalCount, list);
    }

    /**
     *处理逾期的借阅记录
     */
    public void dealWithOverdueBorrowRecords() {
        List<BorrowRecords> overdueBorrowRecords = lambdaQuery().eq(BorrowRecords::getStatus, BorrowRecordType.BORROWING.getCode())
                .le(BorrowRecords::getDueDate, LocalDateTime.now())
                .list();
        for (BorrowRecords overdueBorrowRecord : overdueBorrowRecords) {
            overdueBorrowRecord.setStatus(BorrowRecordType.OVERDUE.getCode());
        }
        updateBatchById(overdueBorrowRecords);
    }

    // 处理最近7天数据
    private void handleLast7Days(LineBarChart chart) {
        generateChartData(chart, 7);
    }

    private void handleLast30Days(LineBarChart chart) {
        generateChartData(chart, 30);
    }

    /**
     * 通用方法：根据指定天数生成折线图/柱状图数据
     */
    private void generateChartData(LineBarChart chart, int days) {
        LocalDate endDate = LocalDate.now();
        LocalDate startDate = endDate.minusDays(days - 1); // 包含今天共 N 天
        //查询出来的结果有偏差(猜测是时区转换的原因），所以加2天
        List<Map<String, Object>> result = baseMapper.selectBorrowCountByDateRange(
                Timestamp.valueOf(startDate.atStartOfDay()),
                Timestamp.valueOf(endDate.plusDays(2).atStartOfDay())
        );
        Map<LocalDate, Long> dateCountMap = result.stream()
                .collect(Collectors.toMap(
                        map -> ((java.sql.Date) map.get("date")).toLocalDate(),
                        map -> ((Number) map.get("count")).longValue()
                ));
        List<String> xAxisData = new ArrayList<>();
        List<String> seriesData = new ArrayList<>();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd");
        for (int i = 0; i < days; i++) {
            LocalDate currentDate = startDate.plusDays(i);
            xAxisData.add(currentDate.format(formatter));
            seriesData.add(String.valueOf(dateCountMap.getOrDefault(currentDate, 0L)));
        }
        chart.setXAxisData(xAxisData);
        chart.setSeriesData(seriesData);
    }

    // 处理最近6个月数据（按月统计）
    private void handleLast6Months(LineBarChart chart) {
        LocalDate endDate = LocalDate.now();
        LocalDate startDate = endDate.minusMonths(6);
        List<Map<String, Object>> result = baseMapper.selectBorrowCountByMonth(
                startDate.atStartOfDay(),
                endDate.plusDays(1).atStartOfDay()
        );
        List<String> xAxisData = new ArrayList<>();
        List<String> seriesData = new ArrayList<>();
        Map<YearMonth, Long> monthCountMap = result.stream()
                .collect(Collectors.toMap(
                        map -> YearMonth.parse((String) map.get("month_key")),
                        map -> (Long) map.get("count")
                ));
        // 生成完整的6个月序列
        YearMonth current = YearMonth.from(startDate);
        for (int i = 1; i <= 6; i++) {
            String monthStr = current.plusMonths(i).format(DateTimeFormatter.ofPattern("yyyy-MM"));
            xAxisData.add(monthStr);
            seriesData.add(String.valueOf(monthCountMap.getOrDefault(current.plusMonths(i), 0L)));
        }
        chart.setXAxisData(xAxisData);
        chart.setSeriesData(seriesData);
    }

    // 处理最近1年数据（按月统计）
    private void handleLast1Year(LineBarChart chart) {
        LocalDate endDate = LocalDate.now();
        LocalDate startDate = endDate.minusMonths(11).withDayOfMonth(1);  // 包含完整的12个月
        List<Map<String, Object>> result = baseMapper.selectBorrowCountByMonth(
                startDate.atStartOfDay(),
                endDate.plusMonths(1).atStartOfDay()
        );
        // 转换为YearMonth为key的Map
        Map<YearMonth, Long> monthCountMap = result.stream()
                .collect(Collectors.toMap(
                        map -> YearMonth.parse((String) map.get("month_key")),
                        map -> (Long) map.get("count")
                ));
        List<String> xAxisData = new ArrayList<>();
        List<String> seriesData = new ArrayList<>();
        // 生成完整的12个月序列
        YearMonth current = YearMonth.from(startDate);
        for (int i = 0; i < 12; i++) {
            String monthStr = current.plusMonths(i).format(DateTimeFormatter.ofPattern("yyyy-MM"));
            xAxisData.add(monthStr);
            seriesData.add(String.valueOf(monthCountMap.getOrDefault(current.plusMonths(i), 0L)));
        }
        chart.setXAxisData(xAxisData);
        chart.setSeriesData(seriesData);
    }

    private Readers checkReader(Long readerId) {
        Readers readers = readersService.getById(readerId);
        if (readers == null) {
            throw new ReaderException("读者不存在！");
        } else if (!readers.getStatus().equals(0)) {
            throw new ReaderException("读者状态异常！");
        }
        return readers;
    }
}
